#include "common.h"
#include <stddef.h>
#include <ctype.h>
#include <string.h>
#include <stdlib.h>
#include <stdint.h>
#include <assert.h>
#include <stdarg.h>
#include <string.h>
#include <stdio.h>
#include <sys/stat.h>

#ifndef WIN32
#define _strdup strdup
#endif

bool str_end_of(const char* s, const char* key)
{
    if (s == NULL || key == NULL)
        return false;
    size_t len1 = strlen(s);
    size_t len2 = strlen(key);
    if (len1 < len2)
        return false;
    return strcmp(s + len1 - len2, key) == 0;
}

bool str_iend_of(const char* s, const char* key)
{
    if (s == NULL || key == NULL)
        return false;
    size_t len1 = strlen(s);
    size_t len2 = strlen(key);
    if (len1 < len2)
        return false;
    for (size_t i = 0; i < len2; ++i)
    {
        if (tolower(s[len1 - len2 + i]) != tolower(key[i]))
            return false;
    }
    return true;
}

int str_ncat(char* s, int size, const char* cat)
{
    int len = (int)strlen(s);
    int len2 = (int)strlen(cat);
    int i = 0;
    for (; i < len2 && i + 1 < size - len; ++i)
    {
        s[i + len] = cat[i];
    }
    s[i + len] = 0;
    return i + len;
}

char* make_string(const char* format, ...)
{
    va_list ap;
    va_start(ap, format);
    size_t count = vsnprintf(NULL, 0, format, ap);
    va_end(ap);
    char* ptr = calloc(1, count + 1);
    if (ptr == NULL) {
        return NULL;
    }
    va_list ap2;
    va_start(ap2, format);
    vsnprintf(ptr, count + 1, format, ap2);
    va_end(ap2);
    return ptr;
}

bool file_exists(const char* path)
{
    struct stat buf;
    return stat(path, &buf) == 0;
}

bool is_directory(const char* path)
{
    struct stat buf;
    return stat(path, &buf) == 0 && buf.st_mode & S_IFDIR;
}

#define MAGIC 0x12131312

typedef struct buffer_t
{
    int32_t size;
    int32_t used;       // bytes
    int32_t buf_size;   // bytes
    int32_t magic;
    unsigned char data[];
} buffer_t;

int sv_init(void** v)
{
    assert(v);
    *v = NULL;
    return 0;
}

int sv_push_size(void** v, void* ptr, size_t size)
{
    struct buffer_t* buffer = NULL;
    if (*v != NULL) {
        buffer = SUB_PTR(*v, sizeof(buffer_t));
    }
    if (buffer == NULL) {
        buffer = malloc(sizeof(*buffer) + size);
        if (buffer == NULL) {
            return -1;
        }
        buffer->magic = MAGIC;
        buffer->size = (int32_t)size;
        buffer->used = (int32_t)size;
        buffer->buf_size = (int32_t)size;
        memcpy(buffer->data, ptr, size);
    } else {
        assert(buffer->magic == MAGIC);
        if (buffer->magic != MAGIC) {
            return -1;
        }
        assert(size == buffer->size);
        if (size != buffer->size) {
            return -1;
        }
        assert(buffer->used <= buffer->buf_size);
        if (buffer->used > buffer->buf_size) {
            return -1;
        }
        assert(buffer->buf_size > 0);
        if (buffer->buf_size <= 0) {
            return -1;
        }
        if (buffer->used >= buffer->buf_size) {
            buffer->buf_size *= 2;
            int new_size = sizeof(*buffer) + buffer->buf_size;
            void* new_buffer = realloc(buffer, new_size);
            if (new_buffer == NULL) {
                return -1;
            }
            buffer = new_buffer;
        }
        memcpy(buffer->data + buffer->used, ptr, size);
        buffer->used += (int)size;
    }
    *v = ADD_PTR(buffer, sizeof(buffer_t));
    return 0;
}

int sv_push_ptr(void** v, const void* ptr)
{
    return sv_push_size(v, (void*)&ptr, sizeof(void*));
}

int sv_count(void* v)
{
    if (v == NULL)
        return 0;
    struct buffer_t* buffer = SUB_PTR(v, sizeof(buffer_t));
    assert(buffer->magic == MAGIC);
    assert(buffer->used == 0 || buffer->size > 0);
    if (buffer->size <= 0)
        return -1;
    return buffer->used / buffer->size;
}

void sv_free(void* v)
{
    if (v == NULL)
        return;
    struct buffer_t* buffer = SUB_PTR(v, sizeof(buffer_t));
    assert(buffer->magic == MAGIC);
    if (buffer->magic != MAGIC) {
        return;
    }
    assert(buffer->used == 0 || buffer->size > 0);
    free(buffer);
}

int svs_init(void* pv)
{
    return sv_init((void**)pv);
}

int svs_push_ptr(void* pv, const void* ptr)
{
    return sv_push_ptr((void**)pv, ptr);
}

int svs_push_size(void* pv, void* ptr, size_t size)
{
    return sv_push_size((void**)pv, ptr, size);
}

DEFINE_SV(ptr_t)

void cmem_init(auto_cmem_t* mp)
{
    sv_init_ptr_t(&mp->ptr);
}

int cmem_push(auto_cmem_t* mp, void* p)
{
    return sv_push_ptr_t(&mp->ptr, p);
}

void* cmem_malloc(auto_cmem_t* mp, size_t size)
{
    void* ptr = malloc(size);
    if (ptr == NULL) {
        return NULL;
    }
    int ret = cmem_push(mp, ptr);
    if (ret != 0) {
        free(ptr);
        return NULL;
    }
    return ptr;
}

void* cmem_calloc(auto_cmem_t* mp, size_t size, size_t count)
{
    void* ptr = malloc(size);
    int ret = cmem_push(mp, ptr);
    if (ret != 0) {
        free(ptr);
        return NULL;
    }
    return ptr;
}

void* cmem_realloc(auto_cmem_t* mp, void* ptr, size_t size)
{
    void* new_ptr = realloc(ptr, size);
    int found = -1;
    for (int i = 0; i < sv_count(mp->ptr); ++i)
    {
        if (mp->ptr[i] == ptr) {
            found = i;
            break;
        }
    }
    if (found >= 0) {
        mp->ptr[found] = new_ptr;
    } else {
        int ret = cmem_push(mp, new_ptr);
        if (ret != 0) {
            free(new_ptr);
            return NULL;
        }
    }
    return new_ptr;
}

char* cmem_strdup(auto_cmem_t* mp, const char* ptr)
{
    char* cs = _strdup(ptr);
    int ret = cmem_push(mp, cs);
    if (ret != 0) {
        free(cs);
        return NULL;
    }
    return cs;
}

void cmem_free(auto_cmem_t* mp)
{
    for (int i = 0; i < sv_count(mp->ptr); ++i)
    {
        if (mp->ptr[i]) {
            free(mp->ptr[i]);
            mp->ptr[i] = NULL;
        }
    }
    sv_free(mp->ptr);
    mp->ptr = NULL;
}
